﻿//------------------------------------------------------------------------------------------------------
// The MIT License (MIT) Copyright (c) 2014 Marton Rusko
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 
// associated documentation files (the "Software"), to deal in the Software without restriction, 
// including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial 
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 
// LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using AutoMapper;
using RM.Banking.Common.Interfaces;

namespace RM.Banking.Common
{
    /// <summary>
    /// Bank Statement Processor base class
    /// </summary>
    public abstract class BankStatementProcessor : IBankStatementProcessor
    {
        #region Private fields

        private readonly HashSet<BankClient> clients;
        private readonly HashSet<BankAccount> accounts;
        private readonly HashSet<BankStatement> statements;
        private readonly HashSet<BankTransaction> transactions;

        #endregion

        #region Public properties
        
        #region Implementation of IBankStatementProcessor

        /// <summary>
        /// Output: Clients 
        /// </summary>
        public IEnumerable<BankClient> Clients
        {
            get { return clients; }
        }

        /// <summary>
        /// Output: Accounts
        /// </summary>
        public IEnumerable<BankAccount> Accounts
        {
            get { return accounts; }
        }

        /// <summary>
        /// Output: Statements
        /// </summary>
        public IEnumerable<BankStatement> Statements
        {
            get { return statements; }
        }

        /// <summary>
        /// Output: Transactions
        /// </summary>
        public IEnumerable<BankTransaction> Transactions
        {
            get { return transactions; }
        }

        #endregion

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a BankStatementProcessor instance
        /// </summary>
        protected BankStatementProcessor()
        {
            clients = new HashSet<BankClient>();
            accounts = new HashSet<BankAccount>();
            statements = new HashSet<BankStatement>();
            transactions = new HashSet<BankTransaction>();
        }

        static BankStatementProcessor()
        {
            Mapper.CreateMap<BankStatementProcessingResult, BankTransaction>()
                .ForMember(dst => dst.Statement,
                           opt => opt.MapFrom(src => Mapper.Map<BankStatement>(src)));
            Mapper.CreateMap<BankStatementProcessingResult, BankStatement>()
                .ForMember(dst => dst.BankAccount,
                           opt => opt.MapFrom(src => Mapper.Map<BankAccount>(src)));
            Mapper.CreateMap<BankStatementProcessingResult, BankAccount>()
                .ForMember(dst => dst.Client,
                           opt => opt.MapFrom(src => Mapper.Map<BankClient>(src)));
            Mapper.CreateMap<BankStatementProcessingResult, BankClient>()
                .ForMember(dst => dst.Title, opt => opt.MapFrom(src => src.ClientTitle))
                .ForMember(dst => dst.Name, opt => opt.MapFrom(src => src.ClientName))
                .ForMember(dst => dst.AddressLine1, opt => opt.MapFrom(src => src.ClientAddress1))
                .ForMember(dst => dst.AddressLine2, opt => opt.MapFrom(src => src.ClientAddress2))
                .ForMember(dst => dst.AddressLine3, opt => opt.MapFrom(src => src.ClientAddress3))
                .ForMember(dst => dst.AddressLine4, opt => opt.MapFrom(src => src.ClientAddress4));
        }

        #endregion

        #region Implementation of IBankStatementProcessor

        /// <summary>
        /// Process a statement from data
        /// </summary>
        /// <param name="data">Byte data of the statement file</param>
        public void Process(byte[] data)
        {
            var results = ProcessCore(data);
            ProcessResults(results);
        }

        /// <summary>
        /// Process a statement from multiple data arrays
        /// </summary>
        /// <param name="datas">Byte datas of the statement files</param>
        public void Process(IEnumerable<byte[]> datas)
        {
            foreach (var data in datas)
                Process(data);
        }

        /// <summary>
        /// Process a statement from a file
        /// </summary>
        /// <param name="filename">Filename of the statement file</param>
        public void Process(string filename)
        {
            Process(File.ReadAllBytes(filename));
        }

        /// <summary>
        /// Process a statement from multiple files
        /// </summary>
        /// <param name="filenames">Filenames of the statement files</param>
        public void Process(IEnumerable<string> filenames)
        {
            foreach (var filename in filenames)
            {
                Process(filename);
            }
        }

        /// <summary>
        /// Clear processed data
        /// </summary>
        public void Clear()
        {
            ClearCore();
        }

        #endregion

        #region Helper methods

        private void ProcessResults(IEnumerable<BankStatementProcessingResult> results)
        {
            if (results == null) throw new ArgumentNullException("results");

            var importedTransactions = results.Select(Mapper.Map<BankTransaction>).Distinct().ToArray();
            var importedStatements = importedTransactions.Select(f => f.Statement).Distinct().ToArray();
            var importedAccounts = importedStatements.Select(f => f.BankAccount).Distinct().ToArray();
            var importedClients = importedAccounts.Select(f => f.Client).Distinct().ToArray();

            //Add new items
            foreach (var t in importedTransactions)
                transactions.Add(t);
            foreach (var statement in importedStatements)
                statements.Add(statement);
            foreach (var account in importedAccounts)
                accounts.Add(account);
            foreach (var client in importedClients)
                clients.Add(client);

            //Apply remapping
            foreach (var t in transactions)
                t.Statement = statements.First(f => f.StatementId == t.Statement.StatementId);
            foreach (var statement in statements)
                statement.BankAccount = accounts.First(f => f.AccountNumber == statement.BankAccount.AccountNumber);
            foreach (var account in accounts)
                account.Client = clients.First(f => f.ClientId == account.Client.ClientId);

        }

        #endregion

        #region Abstract methods

        /// <summary>
        /// Process statement file data
        /// </summary>
        /// <param name="data">File data bytes</param>
        /// <returns>BankStatementProcessingResult items</returns>
        protected abstract IEnumerable<BankStatementProcessingResult> ProcessCore(byte[] data);

        #endregion

        #region Virtual methods

        protected virtual void ClearCore()
        {
            clients.Clear();
        }

        #endregion
    }
}
